#include "include/parser.h"

QList<QString> SPECIAL_TARGETS = QList<QString>()
        << ".PHONY" << ".SUFFIXES" << ".DEFAULT" << ".PRECIOUS"
        << ".INTERMEDIATE" << ".SECONDARY" << ".SECONDEXPANSION"
        << ".DELETE_ON_ERROR" << ".IGNORE" << ".LOW_RESOLUTION_TIME"
        << ".SILENT" << ".EXPORT_ALL_VARIABLES" << ".NOTPARALLEL"
        << ".ONESHELL" << ".POSIX";

imake::Node::Node(QString name) {
    this->name = name;
}

imake::Node::~Node() {
    QList<Node*>::iterator it = deps.begin();
    for ( ; it != deps.end(); ++it) {
        delete *it;
    }
    deps.clear();
}

/**
 * Function adds dependencies to target, also checks and decline recursive
 * dependencies.
 * @param target Target name
 * @param deps List of dependencies
 * @param rootTargets List of target's trees roots
 */
void imake::addDeps(QString target, QList<QString> deps,
                    QList<imake::Node*> &rootTargets) {
    QStack<QString> path;
    QList<QString>::iterator it;
    imake::Node* node = findTarget(target, NULL, path, rootTargets);
    if (!node) {
        node = new imake::Node(target);
        it = deps.begin();
        for ( ; it != deps.end(); ++it) {
            imake::Node* d = new Node(*it);
            node->deps.append(d);
        }
        rootTargets.append(node);
    }
    else {
        it = deps.begin();
        QList<imake::Node*>::iterator nodeIt;
        bool depExists;
        for ( ; it != deps.end(); ++it) {
            depExists = false;
            nodeIt = node->deps.begin();
            for ( ; nodeIt != node->deps.end(); ++nodeIt) {
                depExists = ((*nodeIt)->name == *it);
                if (depExists)
                    break;
            }
            if (!depExists && path.indexOf(*it) == -1) {
                imake::Node* d = new imake::Node(*it);
                node->deps.append(d);
            }
            else {
                // recurse dependency
            }
        }
    }
}

/**
 * Function finds given target in the forest of target-dependencies trees.
 * It also saves path to the target from tree root in "path" variable.
 * @param name Target name
 * @param node Node in which search starts
 * @param path Path stack to find recursive dependencies
 * @param rootTargets List of target's trees roots
 */
imake::Node* imake::findTarget(QString name, imake::Node* node,
                               QStack<QString> &path,
                               QList<imake::Node*> &rootTargets) {
    imake::Node* ret = NULL;
    QList<imake::Node*>::iterator it;
    if (!node) {
        it = rootTargets.begin();
        for ( ; it != rootTargets.end(); ++it) {
            ret = findTarget(name, *it, path, rootTargets);
            if (ret)
                break;
        }
    }
    else {
        path.push(node->name);
        if (node->name == name)
            ret = node;
        else {
            it = node->deps.begin();
            for ( ; it != node->deps.end(); ++it) {
                ret = findTarget(name, *it, path, rootTargets);
                if (ret)
                    break;
            }
            path.pop();
        }
    }
    return ret;
}

/**
 * Function takes filename and read it. Also it replace all includes by
 * its content.
 * @param fileName File name
 */
QString imake::readMakefile (QString fileName) {
    QString ret;
    QString curDir;
    QFile file (fileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return ret;
    QFileInfo fileInfo (file);
    curDir = fileInfo.absolutePath();
    // TODO: check setCurrent return value.
    QDir::setCurrent(curDir);

    QRegExp rx ("^include\\s+(.+)$");
    QRegExp checkSlash ("\\\\$");

    QTextStream in(&file);
    while (!in.atEnd()) {
        QString line = in.readLine();
        int slashInd = checkSlash.indexIn(line);
        if (slashInd != -1)
            while (slashInd != -1)
            {
                QString subLine = in.readLine();
                line.replace(slashInd, 1, subLine);
                slashInd = checkSlash.indexIn(line);
            }
        int ind = rx.indexIn(line);
        if (ind == -1) {
            ret += line + "\n";
        }
        else {
            curDir = QDir::currentPath();
            // TODO: add many includes processing
            ret += readMakefile(rx.cap(1));
            QDir::setCurrent(curDir);
        }
    }

    file.close();
    return ret;
}

/**
 * Function prints all trees to console.
 * @param rootTargets List of target's trees roots
 */
void imake::printAll(QList<imake::Node*> &rootTargets) {
    QList<Node*>::iterator it = rootTargets.begin();
    for ( ; it != rootTargets.end(); ++it) {
        printTree(*it, 0);
    }
}

/**
 * Function prints node of tree and its children.
 * @param node Node pointer
 * @param indent Indent
 */
void imake::printTree(imake::Node* node, int indent) {
    for (int i = 0; i < indent; ++i) std::cout << "  ";
    std::cout << node->name.toStdString() << "\n";
    QList<imake::Node*>::iterator it = node->deps.begin();
    for ( ; it != node->deps.end(); ++it) {
        imake::printTree(*it, indent + 1);
    }
}

// Print specified tree
void imake::printTree_1(Node* node, QTreeWidgetItem * parent)
{
    parent->setText(0, node->name);
    QTreeWidgetItem *item;
    QList<imake::Node*>::iterator it = node->deps.begin();
    for ( ; it != node->deps.end(); ++it) {
        item = new QTreeWidgetItem(parent);
        imake::printTree_1(*it, item);
    }
}

// Print all trees in rootTargets
void imake::printAll_1(QList<Node *> &rootTargets, QTreeWidget * parent)
{
    parent->clear();
    QTreeWidgetItem *topLevelItem;
    QList<Node*>::iterator it = rootTargets.begin();
    for ( ; it != rootTargets.end(); ++it) {
        topLevelItem = new QTreeWidgetItem(parent);
        printTree_1(*it, topLevelItem);
    }
}

// Print variables
void imake::printVar(QMap<QString, QString> &vars, QTableWidget * tableWidget)
{
    tableWidget->clearContents();
    QTableWidgetItem *newItem;
    int row = 0;
    tableWidget->setRowCount(vars.count());
    QMap<QString, QString>::const_iterator i = vars.constBegin();
    while (i != vars.constEnd())
    {
        newItem = new QTableWidgetItem(i.key());
        tableWidget->setItem(row, 0, newItem);
        newItem = new QTableWidgetItem(i.value());
        tableWidget->setItem(row, 1, newItem);
        i++;
        row++;
    }
    tableWidget->resizeColumnsToContents();
}

/**
 * Function clears list of target's trees.
 * @param rootTargets List of target's trees roots
 */
void imake::clearRootTargets(QList<imake::Node*> &rootTargets) {
    QList<Node*>::iterator it = rootTargets.begin();
    for ( ; it != rootTargets.end(); ++it) {
        delete *it;
    }
    rootTargets.clear();
}

/**
 * Function creates dependency trees by given makefile.
 * It saves trees into mi.rootTargets and makefile variables into mi.vars.
 * @param makefile String represantation of makefile
 * @param mi MakeInfo struct
 */
void imake::createTree(QString makefile, imake::MakeInfo &mi) {
    int ind;

    QStringList lines = makefile.split("\n", QString::SkipEmptyParts);

    QRegExp rxSimpleDeps ("^(\\S+)\\s*:(.*)$");

    QRegExp rxVariable ("^(\\S+)\\s*=(.*)$");

    QStringList::iterator line = lines.begin();
    for ( ; line != lines.end(); ++line) {

        ind = rxSimpleDeps.indexIn(*line);
        if (ind == 0) {
            QString target = rxSimpleDeps.cap(1);
            imake::findAndSetVariables(target, mi);
            QString deps = rxSimpleDeps.cap(2).simplified();
            imake::findAndSetVariables(deps, mi);
            // Check is target special
            if (isSpecialTarget(target)) {
                mi.specialTargets.insert(target, deps);
            }
            else {
                QStringList depsList = deps.simplified().split(" ");
                imake::addDeps(target, depsList, mi.rootTargets);
            }
            continue;
        }

        ind = rxVariable.indexIn(*line);
        if (ind == 0) {
            QString name = rxVariable.cap(1);
            QString value = rxVariable.cap(2);
            imake::findAndSetVariables(value, mi);
            mi.vars.insert(name, value);
            continue;
        }
    }
}

/**
 * Function search variables in string and replaces them by its values.
 * If there is no variable in mi.vars map then ${SOME_VAR} will be replaced
 * by empty string.
 * @param str String to search
 * @param mi MakeInfo struct
 */
void imake::findAndSetVariables(QString &str, MakeInfo &mi) {
    QRegExp rx ("\\$(\\{|\\()(.+)(\\}|\\))");
    rx.setMinimal(true);
    int ind = 0;
    while (true) {
        ind = rx.indexIn(str, ind);

        if (ind == -1)
            break;

        QString name = rx.cap(2);

        // If the map contains no item with given key, the function returns
        // a default-constructed value (e.g. an empty string for QString).
        QString value = mi.vars.value(name);
        str = str.replace(ind, name.length() + 3, value);
        ind = ind + value.length();
    }
}

/**
 * Convert string with percent wildcard dependecies to multiple
 * strings. One to each target.
 * @param str String to convert
 */
QString imake::unfoldPercentWildcard(QString str) {
    /// TODO: Make this function

    return QString(str);
}

/**
 * There are many special targets in GNU Make specification.
 * Functions check that name is one of them or not.
 * @param name Target name to check
 */
bool imake::isSpecialTarget(QString name) {
    return SPECIAL_TARGETS.contains(name);
}
